feat(headless): add headless FileUpload primitive#9067
Conversation
Adds a headless, unstyled FileUpload primitive (Root, Trigger, Dropzone, Item, ItemPreview, ItemDelete + useFileUpload hook) supporting file-picker and drag-and-drop selection, single/multiple modes, accept and maxSize filtering with an onReject callback, and image previews. Includes a swingset docs page.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 0fbd138 The changes in this PR will be included in the next version bump. This PR includes changesets to release 0 packagesWhen changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository YAML (base), Repository UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
✅ Files skipped from review due to trivial changes (3)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a new headless ChangesFileUpload Primitive
Estimated code review effort: 4 (Complex) | ~60 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/electron
@clerk/electron-passkeys
@clerk/eslint-plugin
@clerk/expo
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/hono
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/react
@clerk/react-router
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/ui
@clerk/upgrade
@clerk/vue
commit: |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
packages/headless/src/primitives/file-upload/file-upload-root.tsx (1)
45-55: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider adding a
clipPathfallback to the visually-hidden style.The
clip: rect(0,0,0,0)property is legacy CSS; pairing it withclipPath: 'inset(50%)'is the more future-proof visually-hidden pattern.♻️ Suggested addition
const visuallyHiddenInputStyle: CSSProperties = { position: 'absolute', width: 1, height: 1, padding: 0, margin: -1, overflow: 'hidden', clip: 'rect(0, 0, 0, 0)', + clipPath: 'inset(50%)', whiteSpace: 'nowrap', border: 0, };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/headless/src/primitives/file-upload/file-upload-root.tsx` around lines 45 - 55, The visually hidden input style in visuallyHiddenInputStyle should use the more future-proof pattern by adding a clipPath fallback alongside the existing legacy clip rule. Update the style object used by the file-upload root so the hidden input remains accessible across browsers, keeping the current absolute positioning and sizing while including clipPath: 'inset(50%)' in the visuallyHiddenInputStyle definition.packages/headless/src/primitives/file-upload/file-upload.test.tsx (1)
245-257: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider testing object-URL revocation.
The PR summary highlights
FileUploadItemPreviewgenerating/revoking object URLs, but no test assertsURL.revokeObjectURLis called when an item is removed or the preview unmounts. Adding a spy-based assertion would guard against blob-URL leaks regressing silently.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/headless/src/primitives/file-upload/file-upload.test.tsx` around lines 245 - 257, Add a test in the file-upload items suite to verify object-URL cleanup in FileUploadItemPreview: spy on URL.revokeObjectURL, render a file item through Harness, then remove/unmount it and assert revokeObjectURL is called with the created blob URL. Use the existing FileUploadItemPreview/FileUploadItem item rendering flow so the test covers the same lifecycle that generates the preview URL.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/headless/src/primitives/file-upload/file-upload-root.tsx`:
- Around line 78-102: The addFiles callback in file-upload-root is dropping
overflow files in single-file mode without reporting them, so only the first
accepted file is kept and the rest vanish silently. Update addFiles to detect
when multiple is false and more than one file is accepted, then keep the first
file but also surface the remaining accepted files through the rejection path
with an explicit overflow-style rejection so consumers get notified via
onReject. Use the existing addFiles, onReject, accepted, and rejected flow to
preserve current accept/maxSize handling while ensuring every discarded file is
reported.
In `@packages/swingset/src/stories/file-upload.stories.tsx`:
- Around line 28-35: The selected-file list in FileUpload.Stories uses file.name
as the React key, which can collide for duplicate filenames. Update the map in
the file-upload story to use a stable unique identifier per file, either by
deriving a composite key from more file identity or by assigning an id when
files are added, so each FileUpload.Item remains uniquely keyed.
---
Nitpick comments:
In `@packages/headless/src/primitives/file-upload/file-upload-root.tsx`:
- Around line 45-55: The visually hidden input style in visuallyHiddenInputStyle
should use the more future-proof pattern by adding a clipPath fallback alongside
the existing legacy clip rule. Update the style object used by the file-upload
root so the hidden input remains accessible across browsers, keeping the current
absolute positioning and sizing while including clipPath: 'inset(50%)' in the
visuallyHiddenInputStyle definition.
In `@packages/headless/src/primitives/file-upload/file-upload.test.tsx`:
- Around line 245-257: Add a test in the file-upload items suite to verify
object-URL cleanup in FileUploadItemPreview: spy on URL.revokeObjectURL, render
a file item through Harness, then remove/unmount it and assert revokeObjectURL
is called with the created blob URL. Use the existing
FileUploadItemPreview/FileUploadItem item rendering flow so the test covers the
same lifecycle that generates the preview URL.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Repository UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: d9d1fb65-8b21-405e-a414-fcfdb67bc724
📒 Files selected for processing (20)
.changeset/headless-file-upload.mdpackages/headless/README.mdpackages/headless/package.jsonpackages/headless/src/primitives/file-upload/README.mdpackages/headless/src/primitives/file-upload/accept.tspackages/headless/src/primitives/file-upload/file-upload-context.tspackages/headless/src/primitives/file-upload/file-upload-dropzone.tsxpackages/headless/src/primitives/file-upload/file-upload-item-delete.tsxpackages/headless/src/primitives/file-upload/file-upload-item-preview.tsxpackages/headless/src/primitives/file-upload/file-upload-item.tsxpackages/headless/src/primitives/file-upload/file-upload-root.tsxpackages/headless/src/primitives/file-upload/file-upload-trigger.tsxpackages/headless/src/primitives/file-upload/file-upload.test.tsxpackages/headless/src/primitives/file-upload/index.tspackages/headless/src/primitives/file-upload/parts.tspackages/headless/vite.config.tspackages/swingset/src/components/DocsViewer.tsxpackages/swingset/src/lib/registry.tspackages/swingset/src/stories/file-upload.mdxpackages/swingset/src/stories/file-upload.stories.tsx
In single-file mode, extra files from a multi-file drop were kept-first and silently discarded. Report them through onReject with a new 'overflow' rejection reason. Also key the swingset preview list by a composite file identity to avoid duplicate-filename collisions.
Description
Adds a new headless, unstyled
FileUploadcompound primitive to@clerk/headless(following the existing Accordion/Collapsible pattern), composed ofRoot,Trigger,Dropzone,Item,ItemPreview, andItemDeleteparts plus auseFileUploadhook, supporting file-picker and drag-and-drop selection, single/multiple modes,acceptandmaxSizefiltering with anonRejectcallback, and object-URL image previews. It ships zero styles (all state is exposed viadata-cl-*attributes) and keeps the hidden file input out of the a11y tree so theTriggerbutton is the accessible control. Also adds a swingset docs page (Primitives layer) and wires the new subpath export. To test:pnpm --filter @clerk/headless test(24 FileUpload cases pass, including accessibility) and view the page viapnpm dev:swingsetat/components/file-upload.Docs: https://swingset-git-headless-file-upload-component.clerkstage.dev/primitives/file-upload
Summary by CodeRabbit
Summary of updates
New Features
./file-uploadsubpath export so the primitive is available for import.Bug Fixes
onRejectwithreason: "overflow"instead of being discarded silently.onRejectreason reporting for accept/size mismatches and ensured accept checks run before size validation.Documentation / Tests